package com.example.chatbackend.service;

import com.alibaba.nls.client.protocol.InputFormatEnum;
import com.alibaba.nls.client.protocol.NlsClient;
import com.alibaba.nls.client.protocol.OutputFormatEnum;
import com.alibaba.nls.client.protocol.SampleRateEnum;
import com.alibaba.nls.client.protocol.asr.SpeechTranscriber;
import com.alibaba.nls.client.protocol.asr.SpeechTranscriberListener;
import com.alibaba.nls.client.protocol.asr.SpeechTranscriberResponse;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizer;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerListener;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Service
public class SpeechService implements DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(SpeechService.class);

    @Value("${nls.appKey:}")
    private String nlsAppKey;

    @Value("${aliyun.accessKeyId:}")
    private String accessKeyId;

    @Value("${aliyun.accessKeySecret:}")
    private String accessKeySecret;

    @Value("${nls.gatewayUrl:wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1}")
    private String gatewayUrl;

    private volatile NlsClient nlsClient;
    private volatile String cachedToken;
    private volatile long cachedExpireSeconds;

    private NlsClient getClient() throws IOException {
        if (nlsClient != null) return nlsClient;
        synchronized (this) {
            if (nlsClient != null) return nlsClient;
            String akId = accessKeyId != null && !accessKeyId.isEmpty() ? accessKeyId : System.getenv("ALIYUN_AK_ID");
            String akSecret = accessKeySecret != null && !accessKeySecret.isEmpty() ? accessKeySecret : System.getenv("ALIYUN_AK_SECRET");
            String appKey = nlsAppKey != null && !nlsAppKey.isEmpty() ? nlsAppKey : System.getenv("NLS_APP_KEY");
            String url = gatewayUrl != null && !gatewayUrl.isEmpty() ? gatewayUrl : System.getenv().getOrDefault("NLS_GATEWAY_URL", "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1");
            if (akId == null || akSecret == null || appKey == null) {
                throw new IOException("Missing NLS credentials (appKey/accessKeyId/accessKeySecret)");
            }
            ensureAccessToken(akId, akSecret);
            logger.info("Aliyun NLS token acquired, expire at {}", cachedExpireSeconds);
            nlsClient = (url == null || url.isEmpty()) ? new NlsClient(cachedToken) : new NlsClient(url, cachedToken);
            return nlsClient;
        }
    }

    private void ensureAccessToken(String akId, String akSecret) throws IOException {
        long nowSec = System.currentTimeMillis() / 1000;
        if (cachedToken != null && cachedExpireSeconds - nowSec > 60) {
            return;
        }
        try {
            AliyunPopTokenClient.TokenResult tr = AliyunPopTokenClient.createToken(akId, akSecret);
            this.cachedToken = tr.token;
            this.cachedExpireSeconds = tr.expireTime;
        } catch (Exception e) {
            throw new IOException("Failed to get Aliyun NLS token via POP: " + e.getMessage(), e);
        }
    }

    public String transcribePcm16kMono(byte[] pcmData, long timeoutMs) throws Exception {
        NlsClient client = getClient();
        String appKey = nlsAppKey != null && !nlsAppKey.isEmpty() ? nlsAppKey : System.getenv("NLS_APP_KEY");
        CountDownLatch finished = new CountDownLatch(1);
        StringBuilder finalText = new StringBuilder();
        SpeechTranscriberListener listener = new SpeechTranscriberListener() {
            @Override
            public void onTranscriptionResultChange(SpeechTranscriberResponse response) { }

            @Override
            public void onTranscriberStart(SpeechTranscriberResponse response) { }

            @Override
            public void onSentenceBegin(SpeechTranscriberResponse response) { }

            @Override
            public void onSentenceEnd(SpeechTranscriberResponse response) {
                if (response.getTransSentenceText() != null) {
                    if (finalText.length() > 0) finalText.append(" ");
                    finalText.append(response.getTransSentenceText());
                }
            }

            @Override
            public void onTranscriptionComplete(SpeechTranscriberResponse response) {
                finished.countDown();
            }

            @Override
            public void onFail(SpeechTranscriberResponse response) {
                logger.warn("ASR failed: status={} text={} taskId={}", response.getStatus(), response.getStatusText(), response.getTaskId());
                finished.countDown();
            }
        };

        SpeechTranscriber transcriber = new SpeechTranscriber(client, listener);
        transcriber.setAppKey(appKey);
        transcriber.setFormat(InputFormatEnum.PCM);
        transcriber.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
        transcriber.setEnableIntermediateResult(false);
        transcriber.setEnablePunctuation(true);
        transcriber.setEnableITN(false);
        transcriber.start();

        int offset = 0;
        int frame = 3200; // 100ms for 16k,16bit,mono
        while (offset < pcmData.length) {
            int len = Math.min(frame, pcmData.length - offset);
            ByteArrayInputStream chunk = new ByteArrayInputStream(pcmData, offset, len);
            transcriber.send(chunk);
            offset += len;
            // simulate real-time pacing to improve ASR stability
            Thread.sleep(10);
        }
        transcriber.stop();
        finished.await(timeoutMs <= 0 ? 15000 : timeoutMs, TimeUnit.MILLISECONDS);
        transcriber.close();
        return finalText.toString();
    }

    public byte[] synthesizeWav16k(String text, long timeoutMs) throws Exception {
        NlsClient client = getClient();
        String appKey = nlsAppKey != null && !nlsAppKey.isEmpty() ? nlsAppKey : System.getenv("NLS_APP_KEY");
        CompletableFuture<byte[]> future = new CompletableFuture<>();
        SpeechSynthesizerListener listener = new SpeechSynthesizerListener() {
            private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            private boolean first = true;

            @Override
            public void onComplete(SpeechSynthesizerResponse response) {
                future.complete(buffer.toByteArray());
            }

            @Override
            public void onMessage(ByteBuffer message) {
                try {
                    if (first) { first = false; }
                    byte[] bytesArray = new byte[message.remaining()];
                    message.get(bytesArray, 0, bytesArray.length);
                    buffer.write(bytesArray);
                } catch (IOException e) {
                    future.completeExceptionally(e);
                }
            }

            @Override
            public void onFail(SpeechSynthesizerResponse response) {
                future.completeExceptionally(new IOException("TTS failed: " + response.getStatus() + ", " + response.getStatusText()));
            }
        };

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(client, listener);
        synthesizer.setAppKey(appKey);
        synthesizer.setFormat(OutputFormatEnum.WAV);
        synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
        synthesizer.setVoice("siyue");
        synthesizer.setPitchRate(0);
        synthesizer.setSpeechRate(0);
        synthesizer.setText(text);
        synthesizer.addCustomedParam("enable_subtitle", false);
        synthesizer.start();
        byte[] data = future.get(timeoutMs <= 0 ? 20000 : timeoutMs, TimeUnit.MILLISECONDS);
        synthesizer.close();
        return data;
    }

    @Override
    public void destroy() {
        if (nlsClient != null) {
            try { nlsClient.shutdown(); } catch (Exception ignored) {}
        }
    }
}


